libobs_wrapper\data\output/
mod.rs1use std::sync::{Arc, RwLock};
2use std::{ffi::CStr, ptr};
3
4use anyhow::bail;
5use getters0::Getters;
6use libobs::obs_output;
7
8use crate::enums::ObsOutputStopSignal;
9use crate::runtime::ObsRuntime;
10use crate::unsafe_send::Sendable;
11use crate::utils::{AudioEncoderInfo, OutputInfo, VideoEncoderInfo};
12use crate::{impl_obs_drop, impl_signal_manager, run_with_obs};
13
14use crate::{
15 encoders::{audio::ObsAudioEncoder, video::ObsVideoEncoder},
16 utils::{ObsError, ObsString},
17};
18
19use super::ObsData;
20
21mod replay_buffer;
22pub use replay_buffer::*;
23
24#[derive(Debug)]
25struct _ObsOutputDropGuard {
26 output: Sendable<*mut obs_output>,
27 runtime: ObsRuntime,
28}
29
30impl_obs_drop!(_ObsOutputDropGuard, (output), move || unsafe {
31 libobs::obs_output_release(output);
32});
33
34#[derive(Debug, Getters, Clone)]
35#[skip_new]
36pub struct ObsOutputRef {
47 pub(crate) signal_manager: Arc<ObsOutputSignals>,
49
50 pub(crate) settings: Arc<RwLock<Option<ObsData>>>,
52
53 pub(crate) hotkey_data: Arc<RwLock<Option<ObsData>>>,
55
56 #[get_mut]
58 pub(crate) curr_video_encoder: Arc<RwLock<Option<Arc<ObsVideoEncoder>>>>,
59
60 #[get_mut]
62 pub(crate) audio_encoders: Arc<RwLock<Option<Arc<ObsAudioEncoder>>>>,
63
64 #[skip_getter]
66 pub(crate) output: Sendable<*mut obs_output>,
67
68 pub(crate) id: ObsString,
70
71 pub(crate) name: ObsString,
73
74 #[skip_getter]
75 pub(crate) runtime: ObsRuntime,
76
77 #[skip_getter]
79 _drop_guard: Arc<_ObsOutputDropGuard>,
80}
81
82impl ObsOutputRef {
83 pub(crate) fn new(output: OutputInfo, runtime: ObsRuntime) -> Result<Self, ObsError> {
92 let (output, id, name, settings, hotkey_data) = runtime
93 .run_with_obs_result(|| {
94 let OutputInfo {
95 id,
96 name,
97 settings,
98 hotkey_data,
99 } = output;
100
101 let settings_ptr = match settings.as_ref() {
102 Some(x) => x.as_ptr(),
103 None => Sendable(ptr::null_mut()),
104 };
105
106 let hotkey_data_ptr = match hotkey_data.as_ref() {
107 Some(x) => x.as_ptr(),
108 None => Sendable(ptr::null_mut()),
109 };
110
111 let output = unsafe {
112 libobs::obs_output_create(
113 id.as_ptr().0,
114 name.as_ptr().0,
115 settings_ptr.0,
116 hotkey_data_ptr.0,
117 )
118 };
119
120 if output.is_null() {
121 bail!("Null pointer returned from obs_output_create");
122 }
123
124 Ok((Sendable(output), id, name, settings, hotkey_data))
125 })
126 .map_err(|e| ObsError::InvocationError(e.to_string()))?
127 .map_err(|_| ObsError::NullPointer)?;
128
129 let signal_manager = ObsOutputSignals::new(&output, runtime.clone())?;
130 Ok(Self {
131 settings: Arc::new(RwLock::new(settings)),
132 hotkey_data: Arc::new(RwLock::new(hotkey_data)),
133
134 curr_video_encoder: Arc::new(RwLock::new(None)),
135 audio_encoders: Arc::new(RwLock::new(None)),
136
137 output: output.clone(),
138 id,
139 name,
140
141 _drop_guard: Arc::new(_ObsOutputDropGuard {
142 output,
143 runtime: runtime.clone(),
144 }),
145
146 runtime,
147 signal_manager: Arc::new(signal_manager),
148 })
149 }
150
151 pub fn get_current_video_encoder(&self) -> Result<Option<Arc<ObsVideoEncoder>>, ObsError> {
153 let curr = self
154 .curr_video_encoder
155 .read()
156 .map_err(|e| ObsError::LockError(e.to_string()))?;
157
158 Ok(curr.clone())
159 }
160
161 pub fn create_and_set_video_encoder(
173 &mut self,
174 info: VideoEncoderInfo,
175 ) -> Result<Arc<ObsVideoEncoder>, ObsError> {
176 if self.is_active()? {
178 return Err(ObsError::OutputAlreadyActive);
179 }
180
181 let video_enc = ObsVideoEncoder::new_from_info(info, self.runtime.clone())?;
182
183 self.set_video_encoder(video_enc.clone())?;
184 Ok(video_enc)
185 }
186
187 pub fn set_video_encoder(&mut self, encoder: Arc<ObsVideoEncoder>) -> Result<(), ObsError> {
195 if encoder.encoder.0.is_null() {
196 return Err(ObsError::NullPointer);
197 }
198
199 if self.is_active()? {
200 return Err(ObsError::OutputAlreadyActive);
201 }
202
203 let output = self.output.clone();
204 let encoder_ptr = encoder.as_ptr();
205
206 run_with_obs!(self.runtime, (output, encoder_ptr), move || unsafe {
207 libobs::obs_output_set_video_encoder(output, encoder_ptr);
208 })?;
209
210 self.curr_video_encoder
211 .write()
212 .map_err(|e| ObsError::LockError(e.to_string()))?
213 .replace(encoder);
214
215 Ok(())
216 }
217
218 pub fn update_settings(&mut self, settings: ObsData) -> Result<(), ObsError> {
228 if self.is_active()? {
229 return Err(ObsError::OutputAlreadyActive);
230 }
231
232 let settings_ptr = settings.as_ptr();
233 let output = self.output.clone();
234
235 run_with_obs!(self.runtime, (output, settings_ptr), move || unsafe {
236 libobs::obs_output_update(output, settings_ptr)
237 })?;
238
239 self.settings
240 .write()
241 .map_err(|e| ObsError::LockError(e.to_string()))?
242 .replace(settings);
243 Ok(())
244 }
245
246 pub fn create_and_set_audio_encoder(
259 &mut self,
260 info: AudioEncoderInfo,
261 mixer_idx: usize,
262 ) -> Result<Arc<ObsAudioEncoder>, ObsError> {
263 if self.is_active()? {
265 return Err(ObsError::OutputAlreadyActive);
266 }
267
268 let audio_enc = ObsAudioEncoder::new_from_info(info, mixer_idx, self.runtime.clone())?;
269 self.set_audio_encoder(audio_enc.clone(), mixer_idx)?;
270 Ok(audio_enc)
271 }
272
273 pub fn set_audio_encoder(
282 &mut self,
283 encoder: Arc<ObsAudioEncoder>,
284 mixer_idx: usize,
285 ) -> Result<(), ObsError> {
286 if encoder.encoder.0.is_null() {
287 return Err(ObsError::NullPointer);
288 }
289
290 if self.is_active()? {
291 return Err(ObsError::OutputAlreadyActive);
292 }
293
294 let encoder_ptr = encoder.encoder.clone();
295 let output_ptr = self.output.clone();
296 run_with_obs!(self.runtime, (output_ptr, encoder_ptr), move || unsafe {
297 libobs::obs_output_set_audio_encoder(output_ptr, encoder_ptr, mixer_idx)
298 })?;
299
300 self.audio_encoders
301 .write()
302 .map_err(|e| ObsError::LockError(e.to_string()))?
303 .replace(encoder);
304
305 Ok(())
306 }
307
308 pub fn start(&self) -> Result<(), ObsError> {
315 if self.is_active()? {
316 return Err(ObsError::OutputAlreadyActive);
317 }
318
319 let vid_encoder_ptr = self
321 .curr_video_encoder
322 .read()
323 .map_err(|e| ObsError::LockError(e.to_string()))?
324 .as_ref()
325 .map(|enc| enc.as_ptr())
326 .unwrap_or(Sendable(ptr::null_mut()));
327
328 let audio_encoder_ptr = self
329 .audio_encoders
330 .read()
331 .map_err(|e| ObsError::LockError(e.to_string()))?
332 .as_ref()
333 .map(|enc| enc.encoder.clone())
334 .unwrap_or(Sendable(ptr::null_mut()));
335
336 let output_ptr = self.output.clone();
337 let res = run_with_obs!(
338 self.runtime,
339 (output_ptr, vid_encoder_ptr, audio_encoder_ptr),
340 move || unsafe {
341 libobs::obs_encoder_set_video(vid_encoder_ptr, libobs::obs_get_video());
342 libobs::obs_encoder_set_audio(audio_encoder_ptr, libobs::obs_get_audio());
343 libobs::obs_output_start(output_ptr)
344 }
345 )?;
346
347 if res {
348 return Ok(());
349 }
350
351 let err = run_with_obs!(self.runtime, (output_ptr), move || unsafe {
352 Sendable(libobs::obs_output_get_last_error(output_ptr))
353 })?;
354
355 let c_str = unsafe { CStr::from_ptr(err.0) };
356 let err_str = c_str.to_str().ok().map(|x| x.to_string());
357
358 Err(ObsError::OutputStartFailure(err_str))
359 }
360
361 pub fn pause(&self, pause: bool) -> Result<(), ObsError> {
372 if !self.is_active()? {
373 return Err(ObsError::OutputPauseFailure(Some(
374 "Output is not active.".to_string(),
375 )));
376 }
377
378 let output_ptr = self.output.clone();
379
380 let mut rx = if pause {
381 self.signal_manager.on_pause()?
382 } else {
383 self.signal_manager.on_unpause()?
384 };
385
386 let res = run_with_obs!(self.runtime, (output_ptr), move || unsafe {
387 libobs::obs_output_pause(output_ptr, pause)
388 })?;
389
390 if res {
391 rx.blocking_recv().map_err(|_| ObsError::NoSenderError)?;
392
393 Ok(())
394 } else {
395 let err = run_with_obs!(self.runtime, (output_ptr), move || unsafe {
396 Sendable(libobs::obs_output_get_last_error(output_ptr))
397 })?;
398
399 let c_str = unsafe { CStr::from_ptr(err.0) };
400 let err_str = c_str.to_str().ok().map(|x| x.to_string());
401
402 Err(ObsError::OutputPauseFailure(err_str))
403 }
404 }
405
406 pub fn stop(&mut self) -> Result<(), ObsError> {
415 let output_ptr = self.output.clone();
416 let output_active = run_with_obs!(self.runtime, (output_ptr), move || unsafe {
417 libobs::obs_output_active(output_ptr)
418 })?;
419
420 if !output_active {
421 return Err(ObsError::OutputStopFailure(Some(
422 "Output is not active.".to_string(),
423 )));
424 }
425
426 let mut rx = self.signal_manager.on_stop()?;
427 let mut rx_deactivate = self.signal_manager.on_deactivate()?;
428
429 run_with_obs!(self.runtime, (output_ptr), move || unsafe {
430 libobs::obs_output_stop(output_ptr)
431 })?;
432
433 let signal = rx.blocking_recv().map_err(|_| ObsError::NoSenderError)?;
434
435 log::trace!("Received stop signal: {:?}", signal);
436 if signal != ObsOutputStopSignal::Success {
437 return Err(ObsError::OutputStopFailure(Some(signal.to_string())));
438 }
439
440 rx_deactivate
441 .blocking_recv()
442 .map_err(|_| ObsError::NoSenderError)?;
443
444 Ok(())
445 }
446
447 pub fn is_active(&self) -> Result<bool, ObsError> {
448 let output_ptr = self.output.clone();
449 let output_active = run_with_obs!(self.runtime, (output_ptr), move || unsafe {
450 libobs::obs_output_active(output_ptr)
451 })?;
452
453 Ok(output_active)
454 }
455
456 pub fn as_ptr(&self) -> Sendable<*mut obs_output> {
457 self.output.clone()
458 }
459}
460
461impl_signal_manager!(|ptr| unsafe { libobs::obs_output_get_signal_handler(ptr) }, ObsOutputSignals for ObsOutputRef<*mut libobs::obs_output>, [
462 "start": {},
463 "stop": {code: crate::enums::ObsOutputStopSignal},
464 "pause": {},
465 "unpause": {},
466 "starting": {},
467 "stopping": {},
468 "activate": {},
469 "deactivate": {},
470 "reconnect": {},
471 "reconnect_success": {},
472]);